#include "../tournament.h"
#include "../dish.h"
#include "../chef.h"
#include "../judge.h"
#include "test_utilities.h"
#include "../list.h"
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>

#define ASSERT_NULL(a) ASSERT_TEST((a)==NULL)
#define ASSERT_EQUAL(a,b) ASSERT_TEST((a)==(b))
#define ASSERT_STRING_EQUAL(a,b) ASSERT_TEST(strcmp((a),(b))==0)
#define ASSERT_SUCCESS(a) ASSERT_TEST((a)==TOURNAMENT_SUCCESS)

typedef struct {
	Chef chef1;
	Chef chef2;
	Dish inedible;
	Dish edible;
	Judge judge1;
	Judge judge2;
	Tournament tour;
	List names;
} Examples;

static int taste(Dish dish1,Dish dish2) {
	return dishGetSweetness(dish1)-dishGetSweetness(dish2);
}

static ListElement copyString(ListElement str) {
	char *ret=malloc(strlen(str)+1);
	return ret?strcpy(ret,str):NULL;
}

static void freeString(ListElement str) {
	free(str);
}

static Examples setUp() {
	Examples examples;
	examples.inedible = dishCreate("D1",ENTREE,2,10,10,NULL);
	examples.edible = dishCreate("D2",ENTREE,4,4,4,NULL);
	examples.judge1 = judgeCreate("J1",taste,NULL);
	examples.judge2 = judgeCreate("J2",taste,NULL);
	examples.tour = tournamentCreate(NULL);
	examples.names = listCreate(copyString, freeString);
	Chef chef=chefCreate("C1",NULL);
	tournamentAddChef(examples.tour,chef);
	examples.chef1=tournamentGetChef(examples.tour,"C1");
	chefDestroy(chef);
	chef=chefCreate("C2",NULL);
	tournamentAddChef(examples.tour,chef);
	examples.chef2=tournamentGetChef(examples.tour,"C2");
	chefDestroy(chef);
	tournamentAddJudge(examples.tour,examples.judge1);
	tournamentAddJudge(examples.tour,examples.judge2);
	chefAddDish(examples.chef1,examples.inedible,8,NULL);
	return examples;
}

void tearDown(Examples examples) {
	tournamentDestroy(examples.tour);
	dishDestroy(examples.inedible);
	dishDestroy(examples.edible);
	judgeDestroy(examples.judge1);
	judgeDestroy(examples.judge2);
	listDestroy(examples.names);
}

static bool testTournamentCreate() {
	TournamentResult result=TOURNAMENT_CHEF_ALREADY_EXISTS;
	Tournament tournament1=tournamentCreate(&result);
	ASSERT_SUCCESS(result);
	ASSERT_TEST(tournament1);
	Tournament tournament2=tournamentCreate(NULL);
	ASSERT_TEST(tournament2);
	tournamentDestroy(tournament1);
	tournamentDestroy(tournament2);
	return true;
}

static bool testTournamentCopy() {
	SET_UP(Examples);
	TournamentResult result=TOURNAMENT_CHEF_ALREADY_EXISTS;
	Tournament tournament=tournamentCopy(NULL,&result);
	ASSERT_NULL(tournament);
	ASSERT_EQUAL(result,TOURNAMENT_INVALID_ARGUMENT);
	ASSERT_TEST(tournament=tournamentCopy(examples.tour,&result));
	ASSERT_SUCCESS(result);
	ASSERT_EQUAL(tournamentAddChef(tournament,examples.chef1),
			TOURNAMENT_CHEF_ALREADY_EXISTS);
	tournamentAddJudge(examples.tour,examples.judge1);
	tournamentAddJudge(tournament,examples.judge2);
	tournamentGetJudgesNames(examples.tour,examples.names);
	ASSERT_EQUAL(listGetSize(examples.names),3);
	tournamentGetJudgesNames(tournament,examples.names);
	ASSERT_EQUAL(listGetSize(examples.names),6);
	const char *name=listGetFirst(examples.names);
	ASSERT_STRING_EQUAL(name,"J1");
	name=listGetNext(examples.names);
	ASSERT_STRING_EQUAL(name,"J2");
	name=listGetNext(examples.names);
	ASSERT_STRING_EQUAL(name,"J1");
	name=listGetNext(examples.names);
	ASSERT_STRING_EQUAL(name,"J1");
	name=listGetNext(examples.names);
	ASSERT_STRING_EQUAL(name,"J2");
	name=listGetNext(examples.names);
	ASSERT_STRING_EQUAL(name,"J2");
	tournamentDestroy(tournament);
	TEAR_DOWN();
	return true;
}

static bool testTournamentDestroy() {
	tournamentDestroy(NULL);
	Tournament tournament=tournamentCreate(NULL);
	tournamentDestroy(tournament);
	return true;
}

static bool testTournamentClear() {
	SET_UP(Examples);
	tournamentClear(NULL);
	tournamentClear(examples.tour);
	ASSERT_NULL(tournamentGetChef(examples.tour,"C1"));
	tournamentGetJudgesNames(examples.tour,examples.names);
	ASSERT_EQUAL(listGetSize(examples.names),0);
	TEAR_DOWN();
	return true;
}

static bool testTournamentAddChef() {
	SET_UP(Examples);
	ASSERT_TEST(tournamentGetChef(examples.tour,"C1"));
	ASSERT_EQUAL(tournamentAddChef(examples.tour,NULL),
			TOURNAMENT_INVALID_ARGUMENT);
	ASSERT_EQUAL(tournamentAddChef(NULL,examples.chef1),
			TOURNAMENT_INVALID_ARGUMENT);
	ASSERT_EQUAL(tournamentAddChef(examples.tour,examples.chef1),
			TOURNAMENT_CHEF_ALREADY_EXISTS);
	TEAR_DOWN();
	return true;
}

static bool testTournamentAddJudge() {
	SET_UP(Examples);
	ASSERT_EQUAL(tournamentAddJudge(NULL,examples.judge1),
			TOURNAMENT_INVALID_ARGUMENT);
	ASSERT_EQUAL(tournamentAddJudge(examples.tour,NULL),
			TOURNAMENT_INVALID_ARGUMENT);
	tournamentGetJudgesNames(examples.tour,examples.names);
	ASSERT_EQUAL(listGetSize(examples.names),2);
	ASSERT_SUCCESS(tournamentAddJudge(examples.tour,examples.judge1));
	tournamentGetJudgesNames(examples.tour,examples.names);
	ASSERT_EQUAL(listGetSize(examples.names),5);
	const char *name=listGetFirst(examples.names);
	ASSERT_STRING_EQUAL(name,"J1");
	name=listGetNext(examples.names);
	ASSERT_STRING_EQUAL(name,"J2");
	name=listGetNext(examples.names);
	ASSERT_STRING_EQUAL(name,"J1");
	name=listGetNext(examples.names);
	ASSERT_STRING_EQUAL(name,"J2");
	name=listGetNext(examples.names);
	ASSERT_STRING_EQUAL(name,"J1");
	TEAR_DOWN();
	return true;
}

static bool testTournamentGetChef() {
	SET_UP(Examples);
	ASSERT_NULL(tournamentGetChef(NULL,"C1"));
	ASSERT_NULL(tournamentGetChef(examples.tour,NULL));
	ASSERT_NULL(tournamentGetChef(examples.tour,"C3"));
	Chef chef=tournamentGetChef(examples.tour,"C1");
	ASSERT_TEST(chef);
	ASSERT_EQUAL(chef,examples.chef1);
	TEAR_DOWN();
	return true;
}

static bool testTournamentGetLeadingChef() {
	SET_UP(Examples);
	ASSERT_NULL(tournamentGetLeadingChef(NULL));
	ASSERT_EQUAL(tournamentGetLeadingChef(examples.tour),examples.chef1);
	chefIncrementScore(examples.chef2);
	ASSERT_EQUAL(tournamentGetLeadingChef(examples.tour),examples.chef2);
	chefIncrementScore(examples.chef1);
	ASSERT_EQUAL(tournamentGetLeadingChef(examples.tour),examples.chef1);
	chefIncrementScore(examples.chef1);
	ASSERT_EQUAL(tournamentGetLeadingChef(examples.tour),examples.chef1);
	TEAR_DOWN();
	Tournament tournament = tournamentCreate(NULL);
	ASSERT_TEST(tournament);
	ASSERT_NULL(tournamentGetLeadingChef(tournament));
	tournamentDestroy(tournament);
	return true;
}

static bool testTournamentGetJudgesNames() {
	SET_UP(Examples);
	ASSERT_EQUAL(tournamentGetJudgesNames(NULL,examples.names),
			TOURNAMENT_INVALID_ARGUMENT);
	ASSERT_EQUAL(listGetSize(examples.names),0);
	ASSERT_EQUAL(tournamentGetJudgesNames(examples.tour,NULL),
			TOURNAMENT_INVALID_ARGUMENT);
	ASSERT_SUCCESS(tournamentGetJudgesNames(examples.tour,examples.names));
	ASSERT_EQUAL(listGetSize(examples.names),2);
	const char *name=listGetFirst(examples.names);
	ASSERT_STRING_EQUAL(name,"J1");
	name=listGetNext(examples.names);
	ASSERT_STRING_EQUAL(name,"J2");
	tournamentClear(examples.tour);
	ASSERT_SUCCESS(tournamentGetJudgesNames(examples.tour,examples.names));
	ASSERT_EQUAL(listGetSize(examples.names),2);
	TEAR_DOWN();
	return true;
}

static bool testTournamentCompeteChefs() {
	SET_UP(Examples);
	TournamentResult result=TOURNAMENT_CHEF_ALREADY_EXISTS;
	ASSERT_EQUAL(tournamentCompeteChefs(NULL,
			examples.chef1,examples.chef2,examples.names,&result),0);
	ASSERT_EQUAL(result,TOURNAMENT_INVALID_ARGUMENT);
	result=TOURNAMENT_CHEF_ALREADY_EXISTS;
	ASSERT_EQUAL(tournamentCompeteChefs(examples.tour,
			NULL,examples.chef2,examples.names,&result),0);
	ASSERT_EQUAL(result,TOURNAMENT_INVALID_ARGUMENT);
	result=TOURNAMENT_CHEF_ALREADY_EXISTS;
	ASSERT_EQUAL(tournamentCompeteChefs(examples.tour,
			examples.chef1,NULL,examples.names,&result),0);
	ASSERT_EQUAL(result,TOURNAMENT_INVALID_ARGUMENT);
	ASSERT_EQUAL(tournamentCompeteChefs(examples.tour,
			examples.chef1,examples.chef1,examples.names,&result),0);
	ASSERT_EQUAL(result,TOURNAMENT_SAME_CHEF);
	ASSERT_EQUAL(tournamentCompeteChefs(examples.tour,
			examples.chef1,examples.chef2,examples.names,&result),0);
	ASSERT_EQUAL(result,TOURNAMENT_CHEF_HAS_NO_DISHES);
	chefAddDish(examples.chef2,examples.edible,8,NULL);
	ASSERT_TEST(tournamentCompeteChefs(examples.tour,
			examples.chef1,examples.chef2,examples.names,&result)<0);
	ASSERT_SUCCESS(result);
	chefAddDish(examples.chef1,examples.edible,8,NULL);
	chefAddDish(examples.chef2,examples.inedible,8,NULL);
	result=TOURNAMENT_CHEF_ALREADY_EXISTS;
	ASSERT_TEST(tournamentCompeteChefs(examples.tour,
			examples.chef1,examples.chef2,examples.names,&result)>0);
	ASSERT_SUCCESS(result);
	ASSERT_EQUAL(listGetSize(examples.names),0);
	chefAddDish(examples.chef1,examples.inedible,8,NULL);
	chefAddDish(examples.chef2,examples.inedible,8,NULL);
	result=TOURNAMENT_CHEF_ALREADY_EXISTS;
	ASSERT_TEST(tournamentCompeteChefs(examples.tour,
			examples.chef1,examples.chef2,examples.names,&result)>0);
	ASSERT_SUCCESS(result);
	ASSERT_EQUAL(listGetSize(examples.names),2);
	const char *name=listGetFirst(examples.names);
	ASSERT_STRING_EQUAL(name,"J1");
	name=listGetNext(examples.names);
	ASSERT_STRING_EQUAL(name,"J2");
	chefAddDish(examples.chef1,examples.inedible,8,NULL);
	chefAddDish(examples.chef1,examples.edible,8,NULL);
	chefAddDish(examples.chef2,examples.edible,8,NULL);
	chefAddDish(examples.chef2,examples.edible,8,NULL);
	ASSERT_EQUAL(tournamentCompeteChefs(examples.tour,
			examples.chef1,examples.chef2,examples.names,&result),0);
	ASSERT_EQUAL(result,TOURNAMENT_NO_JUDGES);
	judgeCompareDishes(examples.judge1,examples.inedible,
			examples.chef1,examples.inedible,examples.chef2,NULL);
	result=TOURNAMENT_CHEF_ALREADY_EXISTS;
	tournamentAddJudge(examples.tour,examples.judge1);
	ASSERT_EQUAL(tournamentCompeteChefs(examples.tour,
			examples.chef1,examples.chef2,NULL,&result),0);
	ASSERT_EQUAL(result,TOURNAMENT_NO_JUDGES);
	TEAR_DOWN();
	return true;
}

int main() {
	RUN_TEST(testTournamentCreate);
	RUN_TEST(testTournamentCopy);
	RUN_TEST(testTournamentDestroy);
	RUN_TEST(testTournamentClear);
	RUN_TEST(testTournamentAddChef);
	RUN_TEST(testTournamentAddJudge);
	RUN_TEST(testTournamentGetChef);
	RUN_TEST(testTournamentGetLeadingChef);
	RUN_TEST(testTournamentGetJudgesNames);
	RUN_TEST(testTournamentCompeteChefs);
	return 0;
}
